<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>WebRTC 多人测试页</title>
  <style>
    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      padding: 20px;
      background: #f4f6f8;
      max-width: 1200px;
      margin: 0 auto;
    }
    .card {
      background: white;
      border-radius: 12px;
      padding: 20px;
      margin-bottom: 20px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    }
    input, button {
      padding: 12px;
      margin: 5px;
      font-size: 16px;
      border: 1px solid #ddd;
      border-radius: 6px;
    }
    button {
      background: #007bff;
      color: white;
      cursor: pointer;
      border: none;
    }
    button:hover { opacity: 0.9; }
    button:disabled { background: #ccc; cursor: not-allowed; }

    .video-grid {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 16px;
      margin-top: 16px;
    }
    .video-container {
      background: #000;
      border-radius: 8px;
      overflow: hidden;
      position: relative;
    }
    video {
      width: 100%;
      height: 220px;
      object-fit: cover;
      display: block;
    }
    .video-label {
      position: absolute;
      top: 8px;
      left: 8px;
      background: rgba(0,0,0,0.6);
      color: white;
      padding: 4px 8px;
      border-radius: 4px;
      font-size: 14px;
    }
    .log {
      height: 200px;
      overflow-y: auto;
      background: #f8f9fa;
      border: 1px solid #ddd;
      border-radius: 6px;
      padding: 10px;
      font-family: monospace;
      font-size: 12px;
      white-space: pre-wrap;
    }
  </style>
</head>
<body>

<div class="card">
  <h2>📹 WebRTC 信令测试</h2>
  <p>用于测试你的 Java 信令服务器是否支持多人通话</p>

  <div>
    <input type="text" id="server" placeholder="服务器地址" value="localhost:9223" style="width: 200px;">
    <input type="text" id="roomId" placeholder="会议ID" value="test-conference" style="width: 160px;">
    <input type="text" id="userId" placeholder="你的名字" value="UserA" style="width: 120px;">
    <button onclick="connect()">连接</button>
    <button onclick="disconnect()" style="background: #dc3545;">断开</button>
  </div>

  <div style="margin-top: 10px; font-size: 14px;">
    状态: <span id="status">未连接</span>
  </div>
</div>

<div class="card">
  <h3>本地视频</h3>
  <div class="video-grid">
    <div class="video-container">
      <video id="localVideo" autoplay muted playsinline style="width:100%;height:220px;"></video>
      <div class="video-label">我: <span id="myName">未知</span></div>
    </div>
  </div>
</div>

<div class="card">
  <h3>远程视频</h3>
  <div class="video-grid" id="remoteVideos">
    <!-- 远程视频将动态添加到这里 -->
  </div>
</div>

<div class="card">
  <h3>调试日志</h3>
  <div class="log" id="log"></div>
</div>

<script>
  // ============ 全局变量 ============
  let socket = null;
  let localStream = null;
  let peerConnections = {}; // userId → RTCPeerConnection
  const ICE_SERVERS = [{ urls: 'stun:stun.l.google.com:19302' }];
  const logEl = document.getElementById('log');

  // ============ 工具函数 ============
  function log(msg) {
    const time = new Date().toLocaleTimeString();
    logEl.textContent += `[${time}] ${msg}\n`;
    logEl.scrollTop = logEl.scrollHeight;
    console.log(msg);
  }

  function updateStatus(text, color = 'black') {
    document.getElementById('status').textContent = text;
    document.getElementById('status').style.color = color;
  }

  // ============ 1. 连接信令服务器 ============
  async function connect() {
    const server = document.getElementById('server').value;
    const roomId = document.getElementById('roomId').value.trim();
    const userId = document.getElementById('userId').value.trim();

    if (!roomId || !userId) {
      alert('请输入会议ID和用户名');
      return;
    }

    document.getElementById('myName').textContent = userId;
    log(`🎯 开始连接: ${roomId} / ${userId}`);

    // 获取本地媒体
    try {
      localStream = await navigator.mediaDevices.getUserMedia({
        video: { width: 640, height: 480 },
        audio: true
      });
      document.getElementById('localVideo').srcObject = localStream;
      log('✅ 本地媒体流已获取');
    } catch (err) {
      log('❌ 无法访问摄像头/麦克风: ' + err.message);
      return;
    }

    // 连接 WebSocket
    const wsUrl = `ws://${server}/ws/signaling/${roomId}/${userId}`;
    log(`🔗 连接信令: ${wsUrl}`);

    socket = new WebSocket(wsUrl);

    socket.onopen = () => {
      log('🟢 信令连接成功');
      updateStatus('已连接', 'green');
    };

    socket.onmessage = onSignalingMessage;

    socket.onerror = (err) => {
      log('❌ WebSocket 错误: ' + JSON.stringify(err));
      updateStatus('连接错误', 'red');
    };

    socket.onclose = () => {
      log('🔴 信令连接已关闭');
      updateStatus('已断开', 'gray');
    };
  }

  // ============ 2. 处理信令消息 ============
  // 修改 onSignalingMessage
  async function onSignalingMessage(event) {
    try {
      const data = JSON.parse(event.data);
      log(`📩 收到: ${data.type} | ${JSON.stringify(data).substr(0, 100)}...`);

      switch (data.type) {
        case 'user-joined':
          // 👉 新用户加入，我是现有用户，我来发起连接
          if (data.userId) {
            log(`👥 新用户加入: ${data.userId}`);
            await createPeerConnection(data.userId, true); // 我是发起者
          }
          break;

        case 'existing-user':
          if (data.userId && data.userId !== myUserId) {
            // ✅ 只有当对方先加入时，我才发起连接
            // 但你需要知道谁先谁后？可通过 server 排序或简单规则：userId 字典序
            // 简化：总是由后加入者发起
            // 这里假设 B 和 C 都向 A 发起
            console.log(`[Signal] 发现已有用户: ${data.userId}`);
            await createPeerConnection(data.userId, true);
          }
          break;

        case 'user-left':
          if (data.userId) {
            log(`👋 用户离开: ${data.userId}`);
            closePeerConnection(data.userId);
          }
          break;

        case 'offer':
          if (data.from && data.data) {
            await handleOffer(data.from, data.data);
          }
          break;

        case 'answer':
          if (data.from && data.data) {
            await handleAnswer(data.from, data.data);
          }
          break;

        case 'ice-candidate':
          if (data.from && data.data) {
            await handleCandidate(data.from, data.data);
          }
          break;

        default:
          log(`❓ 未知消息: ${data.type}`);
      }
    } catch (err) {
      log(`❌ 解析消息失败: ${err.message}`);
    }
  }

  // ============ 3. WebRTC 核心逻辑 ============
  async function createPeerConnection(userId, isInitiator = false) {
    // ✅ 避免重复创建
    if (peerConnections[userId]) {
      console.log(`[PC] 已存在与 ${userId} 的连接`);
      return;
    }

    console.log(`[PC] 创建连接: ${userId}, 是否发起者: ${isInitiator}`);
    const pc = new RTCPeerConnection(ICE_SERVERS);
    peerConnections[userId] = pc;

    // 添加本地流
    localStream.getTracks().forEach(track => pc.addTrack(track, localStream));

    // 监听远程流
    pc.ontrack = (event) => {
      console.log(`[PC] 收到 ${userId} 的流`);
      addRemoteVideo(userId, event.streams[0]);
    };

    // ICE 候选
    pc.onicecandidate = (e) => {
      if (e.candidate) {
        sendSignal({
          type: 'ice-candidate',
          to: userId,
          data: e.candidate
        });
      }
    };

    // 只有发起者才创建 offer
    if (isInitiator) {
      try {
        const offer = await pc.createOffer();
        await pc.setLocalDescription(offer);
        sendSignal({
          type: 'offer',
          to: userId,
          data: offer
        });
      } catch (err) {
        console.error(`[PC] 创建 offer 失败: ${err.message}`);
      }
    }
  }

  async function handleOffer(from, offer) {
    let pc = peerConnections[from];

    // ✅ 如果已存在连接，且不是 stable 状态，可能需要关闭旧连接
    if (pc) {
      if (pc.signalingState === 'closed') {
        delete peerConnections[from];
        pc = null;
      } else {
        console.log(`[PC] 已存在连接，但状态: ${pc.signalingState}`);
        // 可选择忽略或关闭重连
        return;
      }
    }

    // 创建新连接（接收方）
    pc = new RTCPeerConnection(ICE_SERVERS);
    peerConnections[from] = pc;

    // 添加本地流
    localStream.getTracks().forEach(track => pc.addTrack(track, localStream));

    pc.ontrack = (event) => {
      addRemoteVideo(from, event.streams[0]);
    };

    pc.onicecandidate = (e) => {
      if (e.candidate) {
        sendSignal({
          type: 'ice-candidate',
          to: from,
          data: e.candidate
        });
      }
    };

    try {
      // ✅ 安全设置 remote offer
      await pc.setRemoteDescription(new RTCSessionDescription(offer));

      // 创建 answer
      const answer = await pc.createAnswer();
      await pc.setLocalDescription(answer);

      sendSignal({
        type: 'answer',
        to: from,
        data: answer
      });
    } catch (err) {
      console.error(`[PC] 处理 offer 失败: ${err.message}`);
    }
  }

  async function handleAnswer(from, answer) {
    const pc = peerConnections[from];
    if (!pc) {
      log(`⚠️ 收到 answer，但无对应连接: ${from}`);
      return;
    }

    if (pc.signalingState === 'closed') return;

    try {
      // 👉 确保当前状态是 have-local-offer
      if (pc.signalingState !== 'have-local-offer') {
        log(`⚠️ 收到 answer 但状态不是 have-local-offer: ${pc.signalingState}`);
        return;
      }

      await pc.setRemoteDescription(new RTCSessionDescription(answer));
      log(`✅ 成功设置 answer 来自: ${from}`);
    } catch (err) {
      log(`❌ 设置 answer 失败: ${err.message}`);
    }
  }

  async function handleCandidate(from, candidate) {
    const pc = peerConnections[from];
    if (pc && pc.signalingState !== 'closed') {
      try {
        await pc.addIceCandidate(new RTCIceCandidate(candidate));
        log(`✅ 添加 ICE 候选: ${from}`);
      } catch (err) {
        log(`❌ 添加 ICE 候选失败: ${err.message}`);
      }
    }
  }

  // ============ 4. UI 管理 ============
  function addRemoteVideo(userId, stream) {
    const containerId = `remote-${userId}`;
    if (document.getElementById(containerId)) return;

    const container = document.createElement('div');
    container.id = containerId;
    container.className = 'video-container';

    const video = document.createElement('video');
    video.srcObject = stream;
    video.autoplay = true;
    video.playsInline = true;

    const label = document.createElement('div');
    label.className = 'video-label';
    label.textContent = userId;

    container.appendChild(video);
    container.appendChild(label);
    document.getElementById('remoteVideos').appendChild(container);
  }

  function closePeerConnection(userId) {
    if (peerConnections[userId]) {
      peerConnections[userId].close();
      delete peerConnections[userId];
    }
    const el = document.getElementById(`remote-${userId}`);
    if (el) el.remove();
  }
  function sendSignal(message) {
    if (socket && socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify(message));
      log(`📤 发送: ${message.type} | ${JSON.stringify(message).substr(0, 100)}...`);
    } else {
      log(`❌ 无法发送消息，WebSocket 状态: ${socket?.readyState}`);
    }
  }

  // ============ 5. 断开连接 ============
  function disconnect() {
    if (socket) {
      socket.close();
      socket = null;
    }
    if (localStream) {
      localStream.getTracks().forEach(t => t.stop());
    }
    Object.keys(peerConnections).forEach(closePeerConnection);
    peerConnections = {};
    log('👋 已断开所有连接');
    updateStatus('已断开', 'gray');
  }

  // ============ 页面卸载 ============
  window.addEventListener('beforeunload', disconnect);
</script>
</body>
</html>